﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Service.Model;

namespace Ebt.Linq
{
	/// <summary>
	/// Implements <see cref="IQueryable"/> interface to route queries between several data sources.
	/// </summary>
	/// <typeparam name="TItem">The type of the content of the data source.</typeparam>
	public class FederatedQuery<TItem> : Query<TItem>, IOrderedQueryable<TItem>
	{
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="getQuery">Returns a set of underlying <see cref="IQueryable"/> if parameter is <c>null</c>,
		///  or single <c>IQueryable</c> that corresponds to the first parameter value otherwise.</param>
        public FederatedQuery(Func<IEnumerable<PropertyValue>, IQueryable<TItem>> getQuery)
			: base(new FederatedQueryProvider(getQuery))
		{
		}
		
		/// <summary>
		/// Implements IQueryProvider interface to route queries between several data sources.
		/// </summary>
		private class FederatedQueryProvider : QueryProvider
		{
			private Func<IEnumerable<PropertyValue>, IQueryable<TItem>> GetQuery { get; set; }

			/// <summary>
			/// Constructor.
			/// </summary>
			/// <param name="getQuery">Returns a set of underlying <see cref="IQueryable"/> if parameter is <c>null</c>,
			///  or single <c>IQueryable</c> that corresponds to the first parameter value otherwise.</param>
            public FederatedQueryProvider(Func<IEnumerable<PropertyValue>, IQueryable<TItem>> getQuery)
			{
				GetQuery = getQuery;
			}

			#region Overrides of QueryProvider

			public override string GetQueryText(Expression expression)
			{
				return expression.ToString();
			}

			public override object Execute(Expression expression)
			{
                return GetResult(expression, (ee, exp) => ee.Provider.CreateQuery(exp));
			}

			public override TResult Execute<TResult>(Expression expression)
			{
                return GetResult(expression, (ee, exp) => ee.Provider.Execute<TResult>(exp));
			}

			#endregion

			private TResult GetResult<TResult>(Expression expression, Func<IQueryable<TItem>, Expression, TResult> execute)
			{
                var v = new FederationExpressionVisitor<TItem>();
                var propertyValues = v.Translate(expression);
                var keyProps = FederationMetadataProvider.GetFederationKeyProperties(typeof(TItem));

                var atomKey = FederationMetadataProvider.GetKeyValues(propertyValues, keyProps).ToList();

                bool isFederationRootQuery = atomKey.Count == 0 && FederationMetadataProvider.IsRootEntity(typeof(TItem));
                if (isFederationRootQuery)
                {
                    atomKey = null;
                }
                else if (atomKey.Count != keyProps.Count)
                {
                    throw new Exception("Federation key could not be resolved.");
                }
				var dbCollection = GetQuery(atomKey);
				return ExecuteAgainstProvider(expression, dbCollection, execute);
			}

			private static TResult ExecuteAgainstProvider<TResult>(Expression expression, IQueryable<TItem> sourceQuery, Func<IQueryable<TItem>, Expression, TResult> execute)
			{
				var fixingVisitor = new ExpressionFixingVisitor(typeof(FederatedQuery<TItem>), sourceQuery);
				var fixedExp = fixingVisitor.Fix(expression);
				return execute(sourceQuery, fixedExp);
			}
		}
	}
}
